/**
 * query配置类型
 * @typedef {Object} QueryConfig
 * @property {number} [fc=3] 功能码，1..4，1:读取保持寄存器，2:读取输入寄存器，3:读取保持寄存器和输入寄存器，4:读取保持寄存器和输入寄存器
 * @property {number} [unitid=1] 设备unitID, modbus设备的unitid
 * @property {number} address 寄存器地址
 * @property {number} quantity 寄存器数量
 */

/**
 * 设备字段配置类型
 * @typedef {Object} DeviceField
 * @property {QueryConfig} query 查询配置
 * @property {string} name 字段名称
 * @property {number} [scale] 倍率位数（正数:小数点左移位数,负数:小数点右移位数）
 * @property {number} [max] 超过最大值都显示最大值
 * @property {number} [min] 超过最小值都显示最小值
 */

/**
 * 设备类型类型
 * @typedef {Object} DeviceType
 * @property {string} name 设备类型名称
 * @property {string} nameCN 设备类型名称（中文）
 * @property {DeviceField[]} fields 字段配置数组
 */

/**
 * 计算规则类型
 * @typedef {Object} CalcRule
 * @property {number} [scale] 倍率位数（正数:小数点左移位数,负数:小数点右移位数）
 * @property {number} [max] 超过最大值都显示最大值
 * @property {number} [min] 超过最小值都显示最小值
 * @property {boolean} [isUnsigned=false] 是否无符号数
 */

/**
 * 唯一ID对应字段信息类型
 * @typedef {Object} UniqIdInfo
 * @property {string} fieldName 字段名称, 例如: 'FP_01:{{field.name}}'
 * @property {number} [scale] 倍率位数（正数:小数点左移位数,负数:小数点右移位数）
 * @property {string} [calcRule] 计算规则key, 例如: 'DO:t'
 */

/**
 * 设备配置表类型
 * @typedef {Object} DeviceConfigs
 * @property {QueryConfig[]} queryQueue 查询队列，类似queryTpl，但是可以配置多个查询
 * @property {{[uniqId: string]: UniqIdInfo}} uniqIdMap 唯一ID对应字段
 * @property {{[calcRuleKey: string]: CalcRule}} [calcRuleMap] 计算规则映射表
 */

/**
 * 设备类型映射表类型,可以设置多个设备类型
 * @typedef {{[deviceType: string]: DeviceType}} DeviceTypeMap
 */

/** 设备类型映射表 @type {DeviceTypeMap} */
const DEVICE_TYPE_MAP = {
  // 设备类型: 功能码
  DO: {
    name: 'DO',
    nameCN: '溶氧设备',
    fields: [
      {
        query: {
          fc: 3,
          address: 17,
          quantity: 1,
        },
        name: 't',
        // 倍率位数（正数:小数点左移位数,负数:小数点右移位数）
        scale: 2,
      },
      {
        query: {
          address: 20,
          quantity: 1,
        },
        name: 'do',
        scale: 2,
        max: 20, // 超过最大值都显示最大值
      },
      {
        query: {
          address: 38,
          quantity: 1,
        },
        name: 'ph',
        scale: 3,
        // isUnsigned: true,
        max: 14, // 超过最大值都显示最大值
        min: 0, // 超过最小值都显示最小值
      },
    ],
  },
  // 其他设备类型...
};

/**
 * 根据modbus设备类型，字段名称，uniqId，获取设备
 * @param {string} deviceType 设备类型
 * @param {string} fieldName 字段名称
 * @param {string} unitID 设备unitID, modbus设备的unitid
 * @returns {DeviceConfigs} 设备配置
 */
function getDeviceConfig(deviceType, fieldName, unitID) {
  const deviceTypeConfig = DEVICE_TYPE_MAP[deviceType];
  if (!deviceTypeConfig) {
    throw new Error(`设备类型${deviceType}不存在`);
  }

  /** @type {DeviceConfigs} */
  const deviceConfigs = {
    queryQueue: [],
    uniqIdMap: {},
  };

  deviceTypeConfig.fields.forEach((fieldConfig) => {
    const { query, name } = fieldConfig;
    const { fc = 3, address, quantity } = query;
    const uniqId = `${unitID}_${fc}_${address}_${quantity}`;
    const calcRuleKey = `${deviceType}:${name}`;
    deviceConfigs.uniqIdMap[uniqId] = {
      fieldName: `${fieldName}:${name}`,
      // scale,
      calcRule: calcRuleKey,
    };

    deviceConfigs.queryQueue.push({
      fc,
      unitid: unitID,
      address,
      quantity,
    });
  });

  return deviceConfigs;
}

/** 生成多个设备配置
 * @param {string} deviceType 设备类型
 * @param {string} fieldNameTpl 字段名称模板, 例如: 'FP_{{device.on}}'
 * @param {number} deviceNum 设备数量
 * @param {number} startUnitID 起始unitID，范围: [1,250]
 * @returns {DeviceConfigs} 设备配置数组
 */
function generateDeviceConfigs(
  deviceType,
  fieldNameTpl,
  deviceNum,
  startUnitID
) {
  const deviceConfigs = {
    queryQueue: [],
    uniqIdMap: {},
  };
  for (let i = 0; i < deviceNum; i++) {
    const unitID = startUnitID + i;
    if (unitID < 1 || unitID > 250) {
      throw new Error(`unitID ${unitID} 超出范围 [1,250]`);
    }
    // 设备序号，个位数补0
    const on = (i + 1).toString().padStart(2, '0');
    const fieldName = fieldNameTpl.replace('{{device.on}}', on);
    const deviceConfig = getDeviceConfig(deviceType, fieldName, unitID);
    deviceConfigs.queryQueue.push(...deviceConfig.queryQueue);
    Object.assign(deviceConfigs.uniqIdMap, deviceConfig.uniqIdMap);
  }
  return deviceConfigs;
}

// 生成设备的全部计算规则
/** @type {{[calcRuleKey: string]: CalcRule}} */
const calcRuleMap = {};
// 合法的计算规则属性
const validCalcRuleProps = ['scale', 'max', 'min', 'isUnsigned'];

Object.keys(DEVICE_TYPE_MAP).forEach((deviceType) => {
  DEVICE_TYPE_MAP[deviceType].fields.forEach((fieldConfig) => {
    const { name } = fieldConfig;
    validCalcRuleProps.forEach((key) => {
      const calcRuleKey = `${deviceType}:${name}`;
      const calcRuleInfo = calcRuleMap[calcRuleKey] || {};
      if (fieldConfig[key] !== undefined) {
        calcRuleMap[calcRuleKey] = {
          ...calcRuleInfo,
          [key]: fieldConfig[key],
        };
      }
    });
  });
});

/**
 * 设备配置表类型
 * 0: 设备类型
 * 1: 字段名称模板
 * 2: 设备数量
 * 3: 起始unitID
 * 示例: ['DO', 'FP_{{device.on}}', 14, 21]
 * @typedef {[string, string, number, number]} Config
 */

/** 设备类型配置 @type {Config[]} */
const configs = [
  ['DO', 'FP_{{device.on}}', 14, 21],
  ['DO', 'BEP_{{device.on}}', 1, 10],
];

// 合并所有设备配置
const allDeviceConfigs = {
  queryQueue: [],
  uniqIdMap: {},
  calcRuleMap,
};

// 生成所有设备配置
configs.forEach((item) => {
  const deviceConfigs = generateDeviceConfigs(...item);
  allDeviceConfigs.queryQueue.push(...deviceConfigs.queryQueue);
  Object.assign(allDeviceConfigs.uniqIdMap, deviceConfigs.uniqIdMap);
});

// 数值解码，根据uniqIdMap key，获取到UniqIdInfo, 在对整数进行小数位移动
/**
 * 根据uniqID解码数值
 * @param {string} uniqID 唯一ID
 * @param {number|string|Buffer} value 数值，支持数字、字符串、16位Buffer、32位Buffer
 * @param {{[uniqId: string]: UniqIdInfo}} uniqIdMap 唯一ID对应字段信息
 * @param {{[calcRuleKey: string]: CalcRule}} calcRuleMap 计算规则映射表
 * @returns {number} 解码后的数值
 */
function decodeByUniqID(uniqID, value, uniqIdMap, calcRuleMap) {
  const uniqIdInfo = uniqIdMap[uniqID];
  if (!uniqIdInfo) {
    throw new Error(`uniqID ${uniqID} 不存在`);
  }
  if (value === void 0 || value === null) {
    return value;
  }

  let { scale, calcRule } = uniqIdInfo;
  const calcRuleInfo = calcRuleMap[calcRule] || {};
  scale = calcRuleInfo.scale || scale || 0;
  const isUnsigned = calcRuleInfo.isUnsigned || false;

  // 如果是无符号16位 Buffer，转换为16位正整数
  if (Buffer.isBuffer(value) && value.length === 2) {
    value = isUnsigned ? value.readUInt16BE(0) : value.readInt16BE(0);
  }
  // 如果是无符号32位 Buffer，转换为32位正整数
  if (Buffer.isBuffer(value) && value.length === 4) {
    value = isUnsigned ? value.readUInt32BE(0) : value.readInt32BE(0);
  }
  // console.log('value', value);
  try {
    value = Number(value);
  } catch (error) {
    throw new Error(`uniqID ${uniqID} 解码值${value} 不是数字`);
  }
  const decodedValue = value / Math.pow(10, scale);
  let { max, min } = uniqIdInfo;
  max = calcRuleInfo.max !== void 0 ? calcRuleInfo.max : max;
  min = calcRuleInfo.min !== void 0 ? calcRuleInfo.min : min;

  if (max !== void 0 && decodedValue > max) {
    return max;
  }
  if (min !== void 0 && decodedValue < min) {
    return min;
  }
  return decodedValue;
}

// -------------------- 测试 -------------------- start
// const test = generateDeviceConfigs('DO', 'FP_{{device.on}}', 14, 21);
// console.log(test);

console.log(allDeviceConfigs);

const decodedValue = decodeByUniqID(
  '21_3_38_1',
  // Buffer.from([0xf0, 0x01]),
  -80000,
  allDeviceConfigs.uniqIdMap,
  allDeviceConfigs.calcRuleMap
);
console.log(decodedValue);
// -------------------- 测试 -------------------- end
